PostMessage APIλ₯Ό μ¬μ©νμ¬ μμ ν ν¬λ‘μ€ μ€λ¦¬μ§ ν΅μ μ νμνμΈμ. μΉ μ ν리μΌμ΄μ μ μ·¨μ½μ μ μννκΈ° μν κΈ°λ₯, 보μ μν λ° λͺ¨λ² μ¬λ‘μ λν΄ μμ보μΈμ.
ν¬λ‘μ€ μ€λ¦¬μ§ ν΅μ : PostMessage APIλ₯Ό μ¬μ©ν 보μ ν¨ν΄
νλ μΉμμ μ ν리μΌμ΄μ
μ μ’
μ’
λ€λ₯Έ μΆμ²μ 리μμ€μ μνΈ μμ©ν΄μΌ ν©λλ€. λμΌ μΆμ² μ μ±
(Same-Origin Policy, SOP)μ μ€ν¬λ¦½νΈκ° λ€λ₯Έ μΆμ²μ 리μμ€μ μ κ·Όνλ κ²μ μ ννλ μ€μν 보μ λ©μ»€λμ¦μ
λλ€. κ·Έλ¬λ ν¬λ‘μ€ μ€λ¦¬μ§ ν΅μ μ΄ ν©λ²μ μΌλ‘ νμν μλ리μ€λ μμ΅λλ€. postMessage APIλ μ΄λ₯Ό λ¬μ±νκΈ° μν μ μ΄λ λ©μ»€λμ¦μ μ 곡νμ§λ§, μ μ¬μ μΈ λ³΄μ μνμ μ΄ν΄νκ³ μ μ ν 보μ ν¨ν΄μ ꡬννλ κ²μ΄ μ€μν©λλ€.
λμΌ μΆμ² μ μ± (SOP) μ΄ν΄νκΈ°
λμΌ μΆμ² μ μ± μ μΉ λΈλΌμ°μ μ κΈ°λ³Έ 보μ κ°λ μ λλ€. μ΄λ μΉ νμ΄μ§κ° ν΄λΉ μΉ νμ΄μ§λ₯Ό μ 곡ν λλ©μΈκ³Ό λ€λ₯Έ λλ©μΈμΌλ‘ μμ²νλ κ²μ μ νν©λλ€. μΆμ²λ μ€ν΄(νλ‘ν μ½), νΈμ€νΈ(λλ©μΈ), ν¬νΈλ‘ μ μλ©λλ€. μ΄ μ€ νλλΌλ λ€λ₯΄λ©΄ λ€λ₯Έ μΆμ²λ‘ κ°μ£Όλ©λλ€. μλ₯Ό λ€μ΄:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
μ΄λ€μ λͺ¨λ λ€λ₯Έ μΆμ²μ΄λ©°, SOPλ μ΄λ€ κ°μ μ§μ μ μΈ μ€ν¬λ¦½νΈ μ κ·Όμ μ νν©λλ€.
PostMessage API μκ°
postMessage APIλ μμ νκ³ μ μ΄λ ν¬λ‘μ€ μ€λ¦¬μ§ ν΅μ λ©μ»€λμ¦μ μ 곡ν©λλ€. μ΄λ₯Ό ν΅ν΄ μ€ν¬λ¦½νΈλ μΆμ²μ κ΄κ³μμ΄ λ€λ₯Έ μ°½(μ: iframe, μ μ°½ λλ ν)μΌλ‘ λ©μμ§λ₯Ό λ³΄λΌ μ μμ΅λλ€. μμ μ°½μ μ΄λ¬ν λ©μμ§λ₯Ό μμ νκ³ κ·Έμ λ°λΌ μ²λ¦¬ν μ μμ΅λλ€.
λ©μμ§λ₯Ό 보λ΄λ κΈ°λ³Έ ꡬ문μ λ€μκ³Ό κ°μ΅λλ€:
otherWindow.postMessage(message, targetOrigin);
otherWindow: λμ μ°½μ λν μ°Έμ‘° (μ:window.parent,iframe.contentWindow, λλwindow.openμμ μ»μ μ°½ κ°μ²΄).message: 보λ΄λ €λ λ°μ΄ν°μ λλ€. μ§λ ¬νν μ μλ λͺ¨λ JavaScript κ°μ²΄κ° κ°λ₯ν©λλ€ (μ: λ¬Έμμ΄, μ«μ, κ°μ²΄, λ°°μ΄).targetOrigin: λ©μμ§λ₯Ό 보λ΄λ €λ μΆμ²λ₯Ό μ§μ ν©λλ€. μ΄λ μ€μν 보μ λ§€κ°λ³μμ λλ€.
μμ μΈ‘μμλ message μ΄λ²€νΈλ₯Ό μμ λκΈ°ν΄μΌ ν©λλ€:
window.addEventListener('message', function(event) {
// ...
});
event κ°μ²΄μλ λ€μκ³Ό κ°μ μμ±μ΄ ν¬ν¨λ©λλ€:
event.data: λ€λ₯Έ μ°½μμ λ³΄λΈ λ©μμ§.event.origin: λ©μμ§λ₯Ό λ³΄λΈ μ°½μ μΆμ².event.source: λ©μμ§λ₯Ό λ³΄λΈ μ°½μ λν μ°Έμ‘°.
보μ μν λ° μ·¨μ½μ
postMessageλ SOP μ νμ μ°ννλ λ°©λ²μ μ 곡νμ§λ§, μ μ€νκ² κ΅¬ννμ§ μμΌλ©΄ μ μ¬μ μΈ λ³΄μ μνμ μ΄λν μλ μμ΅λλ€. μΌλ°μ μΈ μ·¨μ½μ μ λ€μκ³Ό κ°μ΅λλ€:
1. λμ μΆμ² λΆμΌμΉ
event.origin μμ±μ κ²μ¦νμ§ μλ κ²μ μ¬κ°ν μ·¨μ½μ μ
λλ€. μμ μκ° λ©μμ§λ₯Ό λ§Ήλͺ©μ μΌλ‘ μ λ’°νλ©΄ μ΄λ€ μΉμ¬μ΄νΈλ μ
μμ μΈ λ°μ΄ν°λ₯Ό λ³΄λΌ μ μμ΅λλ€. λ©μμ§λ₯Ό μ²λ¦¬νκΈ° μ μ νμ event.originμ΄ μμ μΆμ²μ μΌμΉνλμ§ νμΈν΄μΌ ν©λλ€.
μμ (μ·¨μ½ν μ½λ):
window.addEventListener('message', function(event) {
// μ΄λ κ² νμ§ λ§μΈμ!
processMessage(event.data);
});
μμ (μμ ν μ½λ):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('μ λ’°ν μ μλ μΆμ²λ‘λΆν° λ©μμ§ μμ :', event.origin);
return;
}
processMessage(event.data);
});
2. λ°μ΄ν° μ£Όμ
μμ λ λ°μ΄ν°(event.data)λ₯Ό μ€ν κ°λ₯ν μ½λλ‘ μ·¨κΈνκ±°λ DOMμ μ§μ μ£Όμ
νλ©΄ μ¬μ΄νΈ κ° μ€ν¬λ¦½ν
(XSS) μ·¨μ½μ μΌλ‘ μ΄μ΄μ§ μ μμ΅λλ€. μμ λ λ°μ΄ν°λ μ¬μ©νκΈ° μ μ νμ μ΄κ· (sanitize)νκ³ κ²μ¦ν΄μΌ ν©λλ€.
μμ (μ·¨μ½ν μ½λ):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // μ΄λ κ² νμ§ λ§μΈμ!
}
});
μμ (μμ ν μ½λ):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // μ μ ν μ΄κ· ν¨μλ₯Ό ꡬννμΈμ
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// μ¬κΈ°μ κ°λ ₯ν μ΄κ· λ‘μ§μ ꡬννμΈμ.
// μλ₯Ό λ€μ΄, DOMPurify λλ μ μ¬ν λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νμΈμ
return DOMPurify.sanitize(data);
}
3. μ€κ°μ(MITM) 곡격
μμ νμ§ μμ μ±λ(HTTP)μ ν΅ν΄ ν΅μ μ΄ μ΄λ£¨μ΄μ§λ©΄ MITM 곡격μκ° λ©μμ§λ₯Ό κ°λ‘μ±κ³ μμ ν μ μμ΅λλ€. μμ ν ν΅μ μ μν΄ νμ HTTPSλ₯Ό μ¬μ©νμμμ€.
4. μ¬μ΄νΈ κ° μμ² μμ‘°(CSRF)
μμ μκ° μ μ ν κ²μ¦ μμ΄ μμ λ λ©μμ§λ₯Ό κΈ°λ°μΌλ‘ μμ μ μννλ©΄ 곡격μκ° λ©μμ§λ₯Ό μμ‘°νμ¬ μμ μκ° μλνμ§ μμ μμ μ μννλλ‘ μμΌ μ μμ΅λλ€. λ©μμ§μ λΉλ° ν ν°μ ν¬ν¨νκ³ μμ μΈ‘μμ μ΄λ₯Ό νμΈνλ λ± CSRF λ³΄νΈ λ©μ»€λμ¦μ ꡬννμμμ€.
5. targetOriginμ μμΌλμΉ΄λ μ¬μ©
targetOriginμ *λ‘ μ€μ νλ©΄ λͺ¨λ μΆμ²κ° λ©μμ§λ₯Ό μμ ν μ μμ΅λλ€. μ΄λ μΆμ² κΈ°λ° λ³΄μμ λͺ©μ μ 무λ ₯ννλ―λ‘ μ λμ μΌλ‘ νμν κ²½μ°κ° μλλ©΄ νΌν΄μΌ ν©λλ€. *λ₯Ό μ¬μ©ν΄μΌ νλ κ²½μ°, λ©μμ§ μΈμ¦ μ½λ(MAC)μ κ°μ λ€λ₯Έ κ°λ ₯ν 보μ μ‘°μΉλ₯Ό ꡬνν΄μΌ ν©λλ€.
μμ (νΌν΄μΌ ν μ½λ):
otherWindow.postMessage(message, '*'); // μ λμ μΌλ‘ νμν κ²½μ°κ° μλλ©΄ '*' μ¬μ©μ νΌνμΈμ
보μ ν¨ν΄ λ° λͺ¨λ² μ¬λ‘
postMessageμ κ΄λ ¨λ μνμ μννλ €λ©΄ λ€μ 보μ ν¨ν΄ λ° λͺ¨λ² μ¬λ‘λ₯Ό λ°λ₯΄μμμ€:
1. μ격ν μΆμ² κ²μ¦
μμ μΈ‘μμ νμ event.origin μμ±μ κ²μ¦νμμμ€. 미리 μ μλ μ λ’°ν μ μλ μΆμ² λͺ©λ‘κ³Ό λΉκ΅νμμμ€. λΉκ΅μλ μ격ν λλ±μ±(===)μ μ¬μ©νμμμ€.
2. λ°μ΄ν° μ΄κ· λ° κ²μ¦
postMessageλ₯Ό ν΅ν΄ μμ λ λͺ¨λ λ°μ΄ν°λ μ¬μ©νκΈ° μ μ μ΄κ· νκ³ κ²μ¦νμμμ€. λ°μ΄ν°κ° μ¬μ©λ λ°©μμ λ°λΌ μ μ ν μ΄κ· κΈ°μ (μ: HTML μ΄μ€μΌμ΄ν, URL μΈμ½λ©, μ
λ ₯ κ²μ¦)μ μ¬μ©νμμμ€. HTML μ΄κ· μ μν΄ DOMPurifyμ κ°μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νμμμ€.
3. λ©μμ§ μΈμ¦ μ½λ(MAC)
λ©μμ§μ 무결μ±κ³Ό μ λ’°μ±μ 보μ₯νκΈ° μν΄ λ©μμ§μ λ©μμ§ μΈμ¦ μ½λ(MAC)λ₯Ό ν¬ν¨μν€μμμ€. λ°μ μλ 곡μ λΉλ° ν€λ₯Ό μ¬μ©νμ¬ MACμ κ³μ°νκ³ λ©μμ§μ ν¬ν¨ν©λλ€. μμ μλ λμΌν 곡μ λΉλ° ν€λ₯Ό μ¬μ©νμ¬ MACμ λ€μ κ³μ°νκ³ μμ λ MACκ³Ό λΉκ΅ν©λλ€. μΌμΉνλ©΄ λ©μμ§λ μ λ’°ν μ μκ³ λ³μ‘°λμ§ μμ κ²μΌλ‘ κ°μ£Όλ©λλ€.
μμ (HMAC-SHA256 μ¬μ©):
// λ°μ μ
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// μμ μ
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('μ λ’°ν μ μλ μΆμ²λ‘λΆν° λ©μμ§ μμ :', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('λ©μμ§κ° μ λ’°ν μ μμ΅λλ€!');
processMessage(message); // λ©μμ§ μ²λ¦¬ μ§ν
} else {
console.error('λ©μμ§ μλͺ
κ²μ¦ μ€ν¨!');
}
}
μ€μ: 곡μ λΉλ° ν€λ μμ νκ² μμ±λκ³ μ μ₯λμ΄μΌ ν©λλ€. μ½λμ ν€λ₯Ό νλμ½λ©νμ§ λ§μμμ€.
4. Nonceμ νμμ€ν¬ν μ¬μ©
μ¬μ μ‘ κ³΅κ²©μ λ°©μ§νκΈ° μν΄ λ©μμ§μ κ³ μ ν nonce(ν λ² μ¬μ©λλ μ«μ)μ νμμ€ν¬νλ₯Ό ν¬ν¨μν€μμμ€. μμ μλ nonceκ° μ΄μ μ μ¬μ©λμ§ μμλμ§, κ·Έλ¦¬κ³ νμμ€ν¬νκ° νμ© κ°λ₯ν μκ° νλ μ λ΄μ μλμ§ νμΈν μ μμ΅λλ€. μ΄λ 곡격μκ° μ΄μ μ κ°λ‘μ± λ©μμ§λ₯Ό μ¬μ μ‘νλ μνμ μνν©λλ€.
5. μ΅μ κΆν μμΉ
λ€λ₯Έ μ°½μλ μ΅μνμ νμν κΆνλ§ λΆμ¬νμμμ€. μλ₯Ό λ€μ΄, λ€λ₯Έ μ°½μ΄ λ°μ΄ν° μ½κΈ°λ§ νμνλ€λ©΄ λ°μ΄ν° μ°κΈ°λ₯Ό νμ©νμ§ λ§μμμ€. μ΅μ κΆν μμΉμ μΌλμ λκ³ ν΅μ νλ‘ν μ½μ μ€κ³νμμμ€.
6. μ½ν μΈ λ³΄μ μ μ± (CSP)
μ½ν
μΈ λ³΄μ μ μ±
(CSP)μ μ¬μ©νμ¬ μ€ν¬λ¦½νΈλ₯Ό λ‘λν μ μλ μμ€μ μ€ν¬λ¦½νΈκ° μνν μ μλ μμ
μ μ ννμμμ€. μ΄λ postMessage λ°μ΄ν°μ λΆμ μ ν μ²λ¦¬λ‘ μΈν΄ λ°μν μ μλ XSS μ·¨μ½μ μ μν₯μ μννλ λ° λμμ΄ λ μ μμ΅λλ€.
7. μ λ ₯ κ²μ¦
μμ λ λ°μ΄ν°μ ꡬ쑰μ νμμ νμ κ²μ¦νμμμ€. λͺ νν λ©μμ§ νμμ μ μνκ³ μμ λ λ°μ΄ν°κ° μ΄ νμμ λΆν©νλμ§ νμΈνμμμ€. μ΄λ μκΈ°μΉ μμ λμκ³Ό μ·¨μ½μ μ μλ°©νλ λ° λμμ΄ λ©λλ€.
8. μμ ν λ°μ΄ν° μ§λ ¬ν
λ©μμ§λ₯Ό μ§λ ¬ννκ³ μμ§λ ¬ννκΈ° μν΄ JSONκ³Ό κ°μ μμ ν λ°μ΄ν° μ§λ ¬ν νμμ μ¬μ©νμμμ€. eval()μ΄λ Function()κ³Ό κ°μ΄ μ½λ μ€νμ νμ©νλ νμμ μ¬μ©νμ§ λ§μμμ€.
9. λ©μμ§ ν¬κΈ° μ ν
postMessageλ₯Ό ν΅ν΄ 보λ΄λ λ©μμ§μ ν¬κΈ°λ₯Ό μ ννμμμ€. ν° λ©μμ§λ κ³Όλν 리μμ€λ₯Ό μλΉνκ³ μ μ¬μ μΌλ‘ μλΉμ€ κ±°λΆ κ³΅κ²©μΌλ‘ μ΄μ΄μ§ μ μμ΅λλ€.
10. μ κΈ°μ μΈ λ³΄μ κ°μ¬
μ μ¬μ μΈ μ·¨μ½μ μ μλ³νκ³ ν΄κ²°νκΈ° μν΄ μ½λμ λν μ κΈ°μ μΈ λ³΄μ κ°μ¬λ₯Ό μννμμμ€. postMessage ꡬνμ μΈμ¬ν μ£Όμλ₯Ό κΈ°μΈμ΄κ³ λͺ¨λ 보μ λͺ¨λ² μ¬λ‘κ° μ€μλμλμ§ νμΈνμμμ€.
μμ μλ리μ€: Iframeκ³Ό λΆλͺ¨ νμ΄μ§ κ°μ μμ ν ν΅μ
https://iframe.example.comμ νΈμ€ν
λ iframeμ΄ https://parent.example.comμ νΈμ€ν
λ λΆλͺ¨ νμ΄μ§μ ν΅μ ν΄μΌ νλ μλ리μ€λ₯Ό κ³ λ €ν΄ λ³΄κ² μ΅λλ€. iframeμ μ²λ¦¬λ₯Ό μν΄ μ¬μ©μ λ°μ΄ν°λ₯Ό λΆλͺ¨ νμ΄μ§λ‘ 보λ΄μΌ ν©λλ€.
Iframe (https://iframe.example.com):
// 곡μ λΉλ° ν€ μμ± (μμ ν ν€ μμ± λ°©λ²μΌλ‘ λ체)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
// μ¬μ©μ λ°μ΄ν° κ°μ Έμ€κΈ°
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// λΆλͺ¨ νμ΄μ§λ‘ μ¬μ©μ λ°μ΄ν° μ μ‘
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
λΆλͺ¨ νμ΄μ§ (https://parent.example.com):
// 곡μ λΉλ° ν€ (iframeμ ν€μ μΌμΉν΄μΌ ν¨)
const sharedSecret = 'YOUR_SECURE_SHARED_SECRET';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('μ λ’°ν μ μλ μΆμ²λ‘λΆν° λ©μμ§ μμ :', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('λ©μμ§κ° μ λ’°ν μ μμ΅λλ€!');
// μ¬μ©μ λ°μ΄ν° μ²λ¦¬
console.log('μ¬μ©μ λ°μ΄ν°:', userData);
} else {
console.error('λ©μμ§ μλͺ
κ²μ¦ μ€ν¨!');
}
});
μ€μ μ°Έκ³ μ¬ν:
YOUR_SECURE_SHARED_SECRETμ μμ νκ² μμ±λ 곡μ λΉλ° ν€λ‘ κ΅μ²΄νμμμ€.- 곡μ λΉλ° ν€λ iframeκ³Ό λΆλͺ¨ νμ΄μ§ λͺ¨λμμ λμΌν΄μΌ ν©λλ€.
- μ΄ μμ λ λ©μμ§ μΈμ¦μ μν΄ HMAC-SHA256μ μ¬μ©ν©λλ€.
κ²°λ‘
postMessage APIλ μΉ μ ν리μΌμ΄μ
μμ ν¬λ‘μ€ μ€λ¦¬μ§ ν΅μ μ κ°λ₯νκ² νλ κ°λ ₯ν λꡬμ
λλ€. κ·Έλ¬λ μ μ¬μ μΈ λ³΄μ μνμ μ΄ν΄νκ³ μ΄λ¬ν μνμ μννκΈ° μν΄ μ μ ν 보μ ν¨ν΄μ ꡬννλ κ²μ΄ μ€μν©λλ€. μ΄ κ°μ΄λμμ μ€λͺ
ν 보μ ν¨ν΄κ³Ό λͺ¨λ² μ¬λ‘λ₯Ό λ°λ₯΄λ©΄ postMessageλ₯Ό μμ νκ² μ¬μ©νμ¬ κ²¬κ³ νκ³ μμ ν μΉ μ ν리μΌμ΄μ
μ ꡬμΆν μ μμ΅λλ€.
νμ 보μμ μ΅μ°μ μΌλ‘ μκ°νκ³ μΉ κ°λ°μ μν μ΅μ 보μ λͺ¨λ² μ¬λ‘λ₯Ό μμ§νμμμ€. μ ν리μΌμ΄μ μ΄ μ μ¬μ μΈ μ·¨μ½μ μΌλ‘λΆν° 보νΈλλλ‘ μ κΈ°μ μΌλ‘ μ½λμ 보μ ꡬμ±μ κ²ν νμμμ€.